﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic;
using System.Linq.Expressions;

using Fuse8.DomainFramework.Domain;
using Fuse8.DomainFramework.CacheService;
using Fuse8.DomainFramework.Common;
using Fuse8.DomainFramework.Common.Exceptions;
using Fuse8.DomainFramework.Common.Locator;

namespace Fuse8.DomainFramework.DataAccess
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TRepository"></typeparam>
    public abstract class BaseEntityDataRepository<TEntity, TKey, TRepository> : IEntityDataRepository<TEntity, TKey>
        where TEntity : class, IEntityObject<TKey>
        where TKey : IComparable<TKey>, IEquatable<TKey> 
        where TRepository : class
    {
        #region Fields

        /// <summary>
        /// 
        /// </summary>
        private IEntityDataRepositoryMapper<TEntity, TRepository> _mapper;

        #endregion

        #region Properties

        /// <summary>
        /// 
        /// </summary>
        protected virtual IEntityDataRepositoryMapper<TEntity, TRepository> Mapper
        {
            get
            {
                if (_mapper == null)
                {
                    _mapper = DependencyServiceLocator.Current.GetInstance<IEntityDataRepositoryMapper<TEntity, TRepository>>();
                }

                return _mapper;
            }
            set
            {
                _mapper = value;
            }
        }

        #endregion

        #region IEntityDataRepository<T> Members

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        public virtual void Add(TEntity entityObject)
        {
            Guard.ArgumentNotNull(entityObject, "entityObject");

            var repositoryObject = Mapper.CreateRepositoryEntity();

            if (typeof(TKey) == typeof(Guid))
            {
                entityObject.IncreaseId();
            }

            Mapper.ToRepositoryEntity(entityObject, repositoryObject);

            AddToRepository(repositoryObject);

            Mapper.SetIdentityField(repositoryObject, entityObject);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        public virtual void Save(TEntity entityObject)
        {
            Guard.ArgumentNotNull(entityObject, "entityObject");

            var repositoryObject = Mapper.CreateRepositoryEntity();
            Mapper.ToRepositoryEntity(entityObject, repositoryObject);

            SaveToRepository(repositoryObject);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        public virtual void Remove(TEntity entityObject)
        {
            Guard.ArgumentNotNull(entityObject, "entityObject");

            var repositoryObject = Mapper.CreateRepositoryEntity();
            Mapper.ToRepositoryEntity(entityObject, repositoryObject);

            DeleteFromRepository(repositoryObject);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        public virtual void Remove(TKey id)
        {
            DeleteFromRepository(id);
        }

        /// <summary>
        /// 
        /// </summary>        
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual TEntity FindOne(TKey id)
        {
            return AsQueryable().Where("Id == @0", id).FirstOrDefault();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public virtual TEntity FindOne(Expression<Func<TEntity, bool>> expression)
        {
            Guard.ArgumentNotNull(expression, "expression");

            return AsQueryable().Where(expression).FirstOrDefault();
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="expression"></param>
		/// <param name="selectExpression"></param>
		/// <returns></returns>
    	public virtual TEntity FindOne(Expression<Func<TEntity, bool>> expression, Expression<Func<TEntity, object>> selectExpression)
    	{
    		throw new NotImplementedException();
    	}

    	/// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public virtual IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> expression)
        {
            Guard.ArgumentNotNull(expression, "expression");

            return AsQueryable().Where(expression);
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="expression"></param>
		/// <param name="selectExpression"></param>
		/// <returns></returns>
    	public virtual IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> expression, Expression<Func<TEntity, object>> selectExpression)
    	{
    		throw new NotImplementedException();
    	}

    	/// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public virtual IQueryable<TEntity> FindAll()
        {
            return AsQueryable();
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="selectExpression"></param>
		/// <returns></returns>
    	public virtual IQueryable<TEntity> FindAll(Expression<Func<TEntity, object>> selectExpression)
    	{
    		throw new NotImplementedException();
    	}

    	/// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        public virtual TEntity FindOne(IEntityCommand<TEntity> command)
        {
            Guard.ArgumentNotNull(command, "command");

            return command.ExecuteOne();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        public virtual IQueryable<TEntity> Find(IEntityCommand<TEntity> command)
        {
            Guard.ArgumentNotNull(command, "command");

            return command.Execute();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        public void Execute(IEntityCommand<TEntity> command)
        {
            Guard.ArgumentNotNull(command, "command");

            command.Execute();
        }

        /// <summary>
        /// 
        /// </summary>
        public virtual DataRepositoryCacheOptions CacheOptions
        {
            get
            {
                return DataRepositoryCacheOptions.FullCache;
            }
        }

        #endregion

        #region Abstraction

        /// <summary>
        /// 
        /// </summary>
        /// <param name="repositoryEntity"></param>
        protected abstract void AddToRepository(TRepository repositoryEntity);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="repositoryEntity"></param>
        protected abstract void SaveToRepository(TRepository repositoryEntity);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="repositoryEntity"></param>
        protected abstract void DeleteFromRepository(TRepository repositoryEntity);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        protected abstract void DeleteFromRepository(TKey id);

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        protected abstract IQueryable<TEntity> AsQueryable();

        #endregion
    }
}
